﻿using Engine.Graphics;
using Engine.Graphics.VertexStructures;
using SharpDX;
using SharpDX.Direct3D11;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Engine.FileFormats3D
{
    internal struct Header
    {
        #region Public Fields

        public int numberOfChunks;
        public int numberOfLights;
        public int numberOfMaterials;
        public int numberOfPolygons;
        public int numberOfVertices;
        public int[] otherData;

        #endregion Public Fields

        #region Public Methods

        public static Header FromStream(BinaryReader br)
        {
            Header header = new Header();

            br.BaseStream.Position += 4;
            short val1 = br.ReadInt16();
            short val2 = br.ReadInt16();
            header.numberOfVertices = br.ReadInt32();
            header.numberOfPolygons = br.ReadInt32();
            header.numberOfChunks = br.ReadInt32();
            header.numberOfMaterials = br.ReadInt32();
            header.otherData = new int[]{
                br.ReadInt32()
            };
            header.numberOfLights = br.ReadInt32();
            //br.BaseStream.Position += 12;

            return header;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Chunks: " + this.numberOfChunks.ToString());
            sb.AppendLine("Polygons: " + this.numberOfPolygons.ToString());
            sb.AppendLine("Vertices: " + this.numberOfVertices.ToString());
            sb.AppendLine("Materials: " + this.numberOfMaterials.ToString());
            sb.AppendLine("other: " + this.otherData[0].ToString());
            sb.AppendLine("Lights: " + this.numberOfLights.ToString());
            return sb.ToString();
        }

        #endregion Public Methods
    }

    public class MoxFile : IRenderableObject
    {
        #region Private Fields

        public VertexBufferBinding vertexBufferBinding;
        private int[] assignedMaterialIdForChunk;
        private string[] assignedMaterialIdNameForChunk;
        private Engine engine;
        private string fullPath;
        private Header header;
        private SharpDX.Direct3D11.Buffer ib;
        private SharpDX.Direct3D11.BufferDescription ibDescription;
        private short[] indices;
        private List<MoxMaterial> materials;
        private string[] materilIds;
        private int[] numberOfPolygonsPerMaterial;

        /// <summary>
        /// TextureId where the textures are stored in TextureManager.
        /// </summary>
        private int[] textureIds;

        private TextureManager textureManager;

        private SharpDX.Direct3D11.Buffer vb;
        private SharpDX.Direct3D11.BufferDescription vbDescription;
        private MyOwnVertex.MyPositionNormalTextured[] vertices;

        #endregion Private Fields

        #region Public Constructors

        public MoxFile(Engine engine, TextureManager textureManager)
        {
            this.engine = engine;
            this.textureManager = textureManager;
            this.numberOfPolygonsPerMaterial = new int[0];
            this.textureIds = new int[0];
            this.materials = new List<MoxMaterial>();

            this.ibDescription = new BufferDescription()
            {
                BindFlags = BindFlags.IndexBuffer,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.None,
                SizeInBytes = 0,
                Usage = ResourceUsage.Default
            };
            this.vbDescription = new BufferDescription()
            {
                BindFlags = BindFlags.VertexBuffer,
                CpuAccessFlags = 0,
                SizeInBytes = 0,
                Usage = ResourceUsage.Default
            };
        }

        #endregion Public Constructors

        #region Public Properties

        public int[] AssignedMaterialIdForChunk
        {
            get { return assignedMaterialIdForChunk; }
            set { assignedMaterialIdForChunk = value; }
        }

        public SharpDX.Direct3D11.Buffer IndexBuffer
        {
            get { return this.ib; }
        }

        public List<MoxMaterial> Materials
        {
            get { return materials; }
            set { materials = value; }
        }

        public int[] TextureIds
        {
            get { return textureIds; }
            set { textureIds = value; }
        }

        public SharpDX.Direct3D11.Buffer VertexBuffer
        {
            get { return this.vb; }
        }

        public MyOwnVertex.MyPositionNormalTextured[] Vertices
        {
            get
            {
                return this.vertices;
            }
        }

        #endregion Public Properties

        #region Public Methods

        public static MoxFile FromFile(string fileName, Engine engine, TextureManager textureManager)
        {
            if (!File.Exists(fileName))
                return new MoxFile(engine, textureManager);

            FileStream fs = new FileStream(fileName, FileMode.Open);
            BinaryReader br = new BinaryReader(fs);

            MoxFile moxFile = FromStream(br, engine, textureManager);
            moxFile.fullPath = fileName;

            string moxMaterialFilename = fileName.Substring(0, fileName.Length - 4) + ".mtl";
            MoxMaterialFile moxMaterialFile = MoxMaterialFile.FromFile(moxMaterialFilename);

            string textureFolder = moxFile.textureManager.TextureFolder;
            moxFile.textureIds = new int[moxFile.numberOfPolygonsPerMaterial.Length];

            //moxFile.materials = moxMaterialFile.Materials;
            for (int i = 0; i < moxFile.assignedMaterialIdNameForChunk.Length; i++)
            {
                for (int j = 0; j < moxMaterialFile.Materials.Count; j++)
                {
                    if (moxFile.assignedMaterialIdNameForChunk[i] == moxMaterialFile.Materials[j].Id)
                    {
                        moxFile.assignedMaterialIdForChunk[i] = j;
                        break;
                    }
                }
            }

            for (int i = 0; i < moxFile.assignedMaterialIdNameForChunk.Length; i++)
            {
                int id = moxFile.AssignedMaterialIdForChunk[i];
                moxFile.AssignedMaterialIdForChunk[i] = moxFile.materials.Count; // Get the correct id. when not set and count of materials in mox and mtl are not equal, engine can crash.
                moxFile.materials.Add(moxMaterialFile.Materials[id]);
                if (!File.Exists(moxFile.textureManager.TextureFolder + moxMaterialFile.Materials[id].Tex1Name))
                    moxFile.textureManager.TextureFolder = Path.GetDirectoryName(fileName) + "\\Textures\\";

                moxFile.textureIds[i] = moxFile.textureManager.AddTexture(moxMaterialFile.Materials[id].Tex1Name);

                // set the old texture folder.
                moxFile.textureManager.TextureFolder = textureFolder;
            }

            br.Close();
            fs.Close();

            return moxFile;
        }

        public static MoxFile FromStream(BinaryReader br, Engine engine, TextureManager textureManager)
        {
            MoxFile moxFile = new MoxFile(engine, textureManager);
            moxFile.header = Header.FromStream(br);
            //MessageBox.Show(moxFile.header.ToString());

            Color4 color = new Color4(0.4f, 0.4f, 0.4f, 0.0f);

            // Read Vertices.
            moxFile.vertices = new MyOwnVertex.MyPositionNormalTextured[moxFile.header.numberOfVertices];
            for (int i = 0; i < moxFile.header.numberOfVertices; i++)
            {
                Vector3 position = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()) * 2.0f;
                Vector3 normal = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()) * 2.0f;

                moxFile.vertices[i] = new MyOwnVertex.MyPositionNormalTextured(
                    position,
                    normal,
                    br.ReadSingle(), br.ReadSingle(),
                    0.0f, 0.0f,
                    color);

                br.BaseStream.Position += 8;
            }

            // Read indices.
            moxFile.indices = new short[moxFile.header.numberOfPolygons * 3];
            for (int i = 0; i < moxFile.header.numberOfPolygons * 3; i++)
            {
                moxFile.indices[i] = br.ReadInt16();
            }

            long positionAfter3dData = br.BaseStream.Position;
            if (positionAfter3dData != br.BaseStream.Position)
                br.BaseStream.Position = positionAfter3dData;

            moxFile.numberOfPolygonsPerMaterial = new int[moxFile.header.numberOfChunks];
            moxFile.materilIds = new string[moxFile.header.numberOfMaterials];
            moxFile.assignedMaterialIdNameForChunk = new string[moxFile.header.numberOfChunks];
            moxFile.assignedMaterialIdForChunk = new int[moxFile.header.numberOfChunks];

            if (moxFile.header.otherData[0] > 0) // Version
            {
                for (int i = 0; i < moxFile.header.numberOfChunks; i++)
                {
                    byte[] noIdea = br.ReadBytes(12);
                    string id = "0" + noIdea[5].ToString() + "0" + noIdea[4].ToString();
                    moxFile.assignedMaterialIdNameForChunk[i] = id;

                    moxFile.numberOfPolygonsPerMaterial[i] = br.ReadInt32();
                    byte[] noIdea2 = br.ReadBytes(8);
                }
            }
            else
            {
                for (int i = 0; i < moxFile.header.numberOfChunks; i++)
                {
                    byte[] noIdea = br.ReadBytes(6);
                    string id = "0" + noIdea[3].ToString() + "0" + noIdea[2].ToString();
                    moxFile.assignedMaterialIdNameForChunk[i] = id;

                    moxFile.numberOfPolygonsPerMaterial[i] = br.ReadInt16();
                    byte[] noIdea2 = br.ReadBytes(4);
                }
            }

            for (int i = 0; i < moxFile.header.numberOfMaterials; i++)
            {
                byte[] HeaderForMaterial = br.ReadBytes(48);

                string id = "0" + HeaderForMaterial[1].ToString() + "0" + HeaderForMaterial[0].ToString();
                moxFile.materilIds[i] = id;

                float float1 = br.ReadSingle();
                float float2 = br.ReadSingle();
                byte[] otherData = br.ReadBytes(24);
                try
                {
                    string MaterialName = (new string(br.ReadChars(256)).Split('\0'))[0];
                }
                catch (Exception)
                {
                    Console.WriteLine("Mox-materialname could not be read!");
                }
            }
            moxFile.UpdateVertexBuffer();

            return moxFile;
        }

        public void Dispose()
        {
            //throw new NotImplementedException();
        }

        public int GetPriorityLevel()
        {
            return 50;
        }

        public void Render(int renderStep)
        {
            lock (this.engine.Device.ImmediateContext)
            {
                this.engine.Device.ImmediateContext.InputAssembler.SetVertexBuffers(0, this.vertexBufferBinding);
                this.engine.Device.ImmediateContext.InputAssembler.SetIndexBuffer(this.ib, SharpDX.DXGI.Format.R16_UInt, 0);
                for (int i = 0; i < this.textureIds.Length; i++)
                {
                    this.engine.Device.ImmediateContext.DrawIndexed(this.indices.Length, 0, 0);
                }
            }
        }

        public void RenderInstancing(int subset, int numberOfInstances)
        {
            int offset = 0;
            for (int i = 0; i < subset; i++)
            {
                offset += this.numberOfPolygonsPerMaterial[i];
            }
            lock (this.engine.Device.ImmediateContext)
                this.engine.Device.ImmediateContext.DrawIndexedInstanced(this.numberOfPolygonsPerMaterial[subset] * 3, numberOfInstances, offset * 3, 0, 0);
        }

        #endregion Public Methods

        #region Private Methods

        private void UpdateVertexBuffer()
        {
            if (this.vertices.Length == 0)
                return;

            this.vbDescription.SizeInBytes = MyOwnVertex.MyPositionNormalTextured.SizeInBytes() * this.vertices.Length;
            this.vb = SharpDX.Direct3D11.Buffer.Create(this.engine.Device, this.vertices, this.vbDescription);
            this.vertexBufferBinding = new VertexBufferBinding(this.vb, MyOwnVertex.MyPositionNormalTextured.SizeInBytes(), 0);

            this.ibDescription.SizeInBytes = 2 * this.indices.Length;
            this.ib = SharpDX.Direct3D11.Buffer.Create(this.engine.Device, this.indices, this.ibDescription);
        }

        #endregion Private Methods
    }
}